#!/bin/sh

#
# Generate a build configuration.
#
# Copyright 2022, 2023, and 2024 Odin Kroeger.
#
# This file is part of suCGI.
#
# suCGI is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# suCGI is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
# Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with suCGI. If not, see <https://www.gnu.org/licenses/>.
#

# shellcheck disable=2015

#
# Initialiation
#

set -Cefu
repodir="$(cd -P "$(dirname -- "$0")" && pwd)"
: "${repodir:?}"
readonly repodir
# shellcheck disable=1091
. "$repodir/libutil.sh"	|| exit
init			|| exit


#
# Defaults
#

# Compiler flags to test for. Space-separated list.
cflags=

# Directory to put CGI binaries in.
cgidir=/usr/lib/cgi-dir

# Compilers to look for. Space-separated list of commands.
compilers='gcc clang tcc icx icc c99 cc'

# Configuration file.
conf="conf/prod.env"

# Overwrite existing files?
force=

# Headers to look for. Space-separated list of filenames.
headers='sys/param.h features.h' #security/pam_appl.h shadow.h'

# Libraries to look for. Space-separated list of library names.
#libs=pam

# Linker flags to test for. Space-separated list.
ldflags=

# Be quiet?
quiet=

# Group the webserver runs as.
webgroup=www-data


#
# Constants
#

# Variables to set as macros. Whitespace-separated list.
readonly macros='
	CC
	CPPFLAGS
	CFLAGS
	AR
	ARFLAGS
	LDFLAGS
	LDLIBS
	DESTDIR
	INSTALL
	PREFIX
	bindir
	cgidir
	datadir
	datarootdir
	docdir
	dvidir
	exec_prefix
	htmldir
	includedir
	infodir
	libdir
	libexecdir
	lispdir
	localedir
	localstatedir
	man1dir
	man1ext
	man2dir
	man2ext
	man3dir
	man3ext
	man4dir
	man4ext
	man5dir
	man5ext
	man6dir
	man6ext
	man7dir
	man7ext
	man8dir
	man8ext
	mandir
	oldincludedir
	pdfdir
	prefix
	psdir
	runstatedir
	sbindir
	sharedstatedir
	srcdir
	sysconfdir
	webgroup
	GRP_T
	NGRPS_T
	MAX_UID_VAL
	MAX_GID_VAL
	MAX_GRP_VAL
	MAX_NGRPS_VAL
	HAVE_SYS_PARAM_H
	HAVE_FEATURES_H
	paraflags
	preload
'

# Files by which to identify the source directory. Whitespace-separated list.
readonly srcdirfiles='
	sucgi.c
	Makefile.in
	conf/prod.env
	conf/devel.env
'

# Flags for testing compiler features. Space-separated list of flags.
readonly feattestflags='-std=c99 -pipe'

# Flags for testing conversions. Space-separated list of flags.
readonly wconvflags='-Wconversion'

# Where to save the configuration.
readonly confsave=config.status

# Integer types that may be used for IDs and sizes. Colon-separated list.
integertypes=''
for integerbase in 'int' 'long' 'long long'
do
	for integerprefix in '' 'unsigned '
	do integertypes="$integertypes:$integerprefix$integerbase"
	done
done
integertypes="$integertypes:intmax_t:uintmax_t"
unset integerbase integerprefix
readonly integertypes

# Possible data types for group IDs. Colon-separated list.
readonly grouptypes="gid_t:$integertypes"

# Possible data types for sizes. Colon-separated list.
readonly sizetypes="size_t:$integertypes"


#
# Functions
#

# Add compiler-dependent flags to a set.
addflags() {
	__addflags_var="${1:?}" __addflags_ncomps=0 __addflags_comps=
	shift

	for __addflags_arg
	do
		case $__addflags_arg in (-*)
			break
		esac

		__addflags_ncomps=$((__addflags_ncomps + 1))
		__addflags_comps="$__addflags_comps $__addflags_arg"
	done

	if [ "$__addflags_ncomps" -gt 0 ]
	then
		shift "$__addflags_ncomps"
		for __addflags_comp in $__addflags_comps
		do setadd "${__addflags_var}_${__addflags_comp}" "$@"
		done
	else
		setadd "$__addflags_var" "$@"
	fi

	unset __addflags_arg __addflags_ncomps __addflags_comps \
	      __addflags_comp __addflags_var
}

# Add compiler flags to test for.
addcflags() {
	addflags cflags "$@"
}

# Add the given C preprocessor flags
addcppflags() {
	setadd CPPFLAGS "$@"
}

# Add linker flags to test for.
addldflags() {
	addflags ldflags "$@"
}

# Get a function prototype.
getprototype() {
	"$CC" -E - <<-EOF 2>/dev/null |
	#define _BSD_SOURCE
	#define _DARWIN_C_SOURCE
	#define _GNU_SOURCE
	#define _DEFAULT_SOURCE
	#define __attribute__(attr)
	#include <$1> 
	EOF
	awk -vRS=';' -vfn="${2:?}" '
		$0 !~ /^ *#/ && $0 ~  "[^ ] *" fn " *[\\($]" {
			gsub("[\n\r\t\v ]+", " ");
			print;
		}
	'
}

# Parse an option.
getoptarg() {
	case $1 in
	(-pg)		OPTOPT="$1" OPTARG='' OPTCNT=1 ;;
	(-[cSEg])	OPTOPT="$1" OPTARG='' OPTCNT=1 ;;
	(--*=*) 	OPTOPT="${1%%=*}" OPTARG="${1#*=}" OPTCNT=1 ;;
	(--*|-?)	OPTOPT="$1" OPTARG="${2-}" OPTCNT=2 ;;
	(-*)		OPTARG="${1#-?}" OPTOPT="${1%"$OPTARG"}" OPTCNT=1 ;;
	(*)		err -s2 "%s: not an option" "$1"
	esac
}

# Get the type of a function parameter.
getparamtype() {
	getprototype "${1:?}" "${2:?}" |
	awk -vn="${3:?}" '
		match($0, "\\([^)]*") {
			paramstr = substr($0, RSTART + 1, RLENGTH - 1);
			split(paramstr, params, " *, *");
			print(params[n]);
		}
	'
}

# Add elements to set
setadd() {
	__setadd_set="${1:?}"
	shift

	eval : "\${$__setadd_set=}"
	for __setadd_elem
	do
		eval "inlist '=' \"\$__setadd_elem\" \${$__setadd_set}" ||
		eval "$__setadd_set=\"\${$__setadd_set# } \$__setadd_elem\""
	done
}

# Test compilation.
testcc() (
	: "${testcc_cc:?}" "${testcc_cflags?}"

	exec >/dev/null 2>&1

	case $testcc_cc in
	(clang|gcc)
		# shellcheck disable=2086
		"$CC" -xc -Werror $testcc_cflags -o/dev/null "$@"
		;;
	(tcc)
		# TinyCC unlinks /dev/null when given -o/dev/null.
		# https://savannah.nongnu.org/bugs/index.php?64476
		# shellcheck disable=2086
		"$CC" -Wunsupported \
		      -xc -Werror $testcc_cflags -o"$TMPDIR/a.out" "$@"
		;;
	(icc)
		# shellcheck disable=2086
		"$CC" -diag-enable=10006,10148 -diag-error=10006,10148 \
		      -xc -Werror $testcc_cflags -o/dev/null "$@"
		;;
	(*)
		return 1
		;;
	esac
)

# Test compiler flags.
testflags() {
	testcc "$@" - <<-'EOF'
	int main(void) {return 0;}
	EOF
}

# Replace $1 with $2 if they differ.
swapfiles() {
	: "${1:?}" "${2:?}"

	if ! [ -e "$1" ]
	then
		warn -q 'Creating %s ...' "$1"
	elif ! cmp -s "$1" "$2"
	then
		warn -q 'Updating %s ...' "$1"
	else
		warn -q 'No changes to %s' "$1"
		return
	fi

	mv "$2" "$1"
}


#
# Options
#

for macro in $macros
do
	case $macro in
	(*[!a-z_]*)	: ;;
	(*)		unset "$macro"
	esac
done
unset macro

optcflags=
while [ "$#" -gt 0 ]
do
	case $1 in
	(--)	shift
		break
		;;
	(--help|-h)
		exec cat <<EOF
$progname - generate a build configuration

Usage: $progname [--conf=file|--devel] [--force] [--quiet]
		 [<GNU option> ...] [<compiler flag> ...] [variable=value]
       $progname -h|--help

Options:
    --conf=file	       Read configuration from file (default: $conf).
    --devel	       Short for --conf=conf/devel.env.
    --force	       Overwrite existing files.
    --posix	       Short for --conf=conf/posix.env.
    --quiet	       Be quiet.
    -h, --help	       Show this help screen.

$progname aims to conform to the GNU Coding Standard (chap. 7),
but does not support --build, --host, and --target.

Unrecognised options are passed on to the C compiler.

See docs/build.md for details.

Copyright 2022, 2023, and 2024 Odin Kroeger.
Released under the GNU Affero General Public License.
This programme comes with ABSOLUTELY NO WARRANTY.
EOF
		;;
	(--build|--host|--target|--with-*)
		warn '%s: Not supported' "$1"
		shift
		;;
	(--conf|--conf=*)
		getoptarg "$@"
		shift "$OPTCNT"
		conf="$OPTARG"
		;;
	(--debug)
		set -x
		shift
		;;
	(--devel)
		conf=conf/devel.env
		shift
		;;
	(--enable-*)
		getoptarg "$@"
		shift "$OPTCNT"
		warn '%s: Not supported' "$1"
		;;
	(--exec-prefix|--exec-prefix=*)
		getoptarg "$@"
		shift "$OPTCNT"
		exec_prefix="$OPTARG"
		;;
	(--force)
		force=y
		shift
		;;
	(--posix)
		conf=conf/posix.env
		shift
		;;
	(--quiet)
		quiet=y
		shift
		;;
	(--verbose)
		quiet=
		shift
		;;
	(--*)
		getoptarg "$@"
		var="${OPTOPT#--}"
		# shellcheck disable=2086
		if inlist '=' "$var" $macros
		then eval "$var"='"$OPTARG"'
		else err -s2 '%s: Unknown option' "$OPTOPT"
		fi
		shift 2
		unset var
		;;
	(-[!A-Za-z]*)
		err -s2 '%s: Unknown option' "$1"
		;;
	(-[DU]*)
		setadd CPPFLAGS "$1"
		getoptarg "$@"
		shift "$OPTCNT"
		;;
	(-[IL]*)
		setadd LDFLAGS "$1"
		getoptarg "$@"
		shift "$OPTCNT"
		;;
	(-l*)
		setadd LDLIBS "$1"
		getoptarg "$@"
		shift "$OPTCNT"
		;;
	(-[ox]*)
		getoptarg "$@"
		shift "$OPTCNT"
		warn '%s: Ignored' "$OPTOPT"
		;;
	(-*)	setadd optcflags "$1"
		getoptarg "$@"
		shift "$OPTCNT"
		;;
	(*=*)	variable="${1%%=*}" value="${1#*=}"
		# shellcheck disable=2086
		if inlist '=' "$variable" $macros
		then eval "$variable"='"$value"'
		else warn '%s: Unknown variable' "$variable"
		fi
		unset variable value
		shift
		continue
		;;
	(*)	break
		;;
	esac
	unset OPTARG OPTCNT OPTOPT
done

case $# in
(0) : ;;
(1) warn '%s: Ignoring build type' "$1" ;;
(*) err -s2 'Too many operands'
esac

conffile=
case $conf in
	([./]*)	conffile="$conf" ;;
	(*)	for dir in . ./conf .. ../conf "$repodir" "$repodir/conf"
		do
			case $conf in
			(*.env) fname="$dir/$conf" ;;
			(*)     fname="$dir/$conf.env" ;;
			esac

			if [ -e "$fname" ]
			then
				conffile="$fname"
				break
			fi
		done
esac
unset dir file suffix

[ "$conffile" ] || err '%s: No such configuration' "$conf"

# shellcheck source=conf/devel.env
. "$conffile" || exit
unset conf conffile

for fname in		\
	${DESTDIR-}	\
	${PREFIX-}	\
	${prefix-}	\
	${srcdir-}	\
	${cgidir-}	\
	${exec_prefix-}	\
	${libexecdir-}	\
	${datarootdir-}	\
	${mandir-}	\
	${man8dir-}	\
	${man8ext-}
do
	case $fname in
	(*[!./A-Za-z0-9_-]*) err -s2 '%s: Bad characters' "$fname"
	esac
done

if ! [ "$force" ]
then
	for file in "$confsave" Makefile
	do
		if [ -e "$confsave" ]
		then err '%s: Exists, use --force to overwrite' "$file"
		fi
	done
	unset file
fi

[ "$quiet" ] && exec >/dev/null

# shellcheck disable=2034
readonly force quiet

# shellcheck disable=2086
addcflags $optcflags
unset optcflags


#
# Prelude
#

tmpdir tmp . || exit


#
# Source directory
#

if [ "${srcdir-x}" = x ] && [ "${srcdir-}" != x ]
then
	for dir in . .. "$repodir"
	do
		for file in $srcdirfiles
		do
			if ! [ -e "$dir/$file" ]
			then continue 2
			fi
		done

		srcdir="$dir"
		break
	done
	unset dir
fi

[ "${srcdir-}" ] || err 'Cannot locate source directory'

# TODO: Quote characters that are special to make


#
# Compiler
#

printf 'CC = '

: "${CC:=}"
if ! [ "$CC" ]
then
	for cc in $compilers
	do
		if	command -v "$cc" >/dev/null 2>&1 &&
			"$cc" -v 2>&1 | grep -q "$cc"
		then
			CC="$cc"
			break
		fi
	done

	if ! [ "$CC" ]
	then
		for cc in c99 cc
		do
			if command -v "$cc" >/dev/null 2>&1
			then
				CC="$cc"
				break
			fi
		done
	fi

	unset cc
fi

if ! [ "$CC" ]
then
	printf 'none found\a\n'
	exit 2
fi

# Absolute path.
ccpath="${CC:?}"
readonly ccpath

# Compiler executable.
ccexec="$(basename "${ccpath:?}")"
readonly ccexec

# Compiler name.
case $ccexec in
(musl-*) ccname="${ccexec##musl-}"
esac
ccname="${ccexec%%[!a-z]*}"
readonly ccname

# Compiler command prefix.
case $ccname in
(clang)	ccpre=llvm ;;
(*)	ccpre="$ccname" ;;
esac
readonly ccpre

# Compiler flags useful for testing compiler features.
testcc_cc="$ccname"
testcc_cflags=
# shellcheck disable=2086
for flag in $feattestflags
do testflags $flag && testcc_cflags="$testcc_cflags $flag"
done
unset flag
readonly testcc_cc testcc_cflags

# Check if the compiler detects conversions that may alter values.
if testflags $wconvflags
then ccwconv=y
else ccwconv=
fi

# Output.
printf '%s\n' "$CC"


#
# C preprocessor flags
#

printf 'CPPFLAGS = '

# Build configuration
addcppflags -I.
printf '%s ' "${CPPFLAGS# }"

# Headers
for header in $headers
do
	headervar="$(printf '%s\n' "$header"	|
		     sed 's/[^0-9a-z_]/_/g'   	|
		     tr '[:lower:]' '[:upper:]')"

	if ! [ "$headervar" ]
	then err '%s: Cannot derive header name' "$header"
	else headervar="HAVE_$headervar"
	fi

	eval headeravail="\${$headervar-}"
	if ! [ "$headeravail" ]
	then
		if	"$CC" -E - <<-EOF >/dev/null 2>&1
			#define _BSD_SOURCE
			#define _DARWIN_C_SOURCE
			#define _GNU_SOURCE
			#define _DEFAULT_SOURCE
			#define __attribute__(attr)
			#include <$header>
			EOF
		then headeravail=1 
		else headeravail=0
		fi
	fi

	case $headeravail in
	(0)	addcppflags -U"$headervar"
		printf '%s ' "-U$headervar" ;;
	(1)	addcppflags -D"$headervar"
		printf '%s ' "-D$headervar" ;;
	(*)	err '%s=%s: Neither 0 nor 1' "$headervar" "$headeravail"
	esac

	unset headeravail headervar
done
unset header

# getgrouplist GID data type.
: "${GRP_T=}"
# shellcheck disable=2086
if	! [ "$GRP_T" ] &&
	[ "$ccwconv" ] &&
	testflags -Wincompatible-pointer-types
then
	IFS=:
	for grp_t in $grouptypes
	do
		unset IFS
		[ "$grp_t" ] || continue

		# shellcheck disable=2086
		if testcc $wconvflags -Wincompatible-pointer-types - <<-EOF
			#define _BSD_SOURCE
			#define _DARWIN_C_SOURCE
			#define _DEFAULT_SOURCE
			#define _GNU_SOURCE

			#include <grp.h>
			#include <inttypes.h>
			#include <unistd.h>

			int
			main(void)
			{
			    $grp_t basegid = 0;
			    $grp_t groups[1] = {0};
			    int n = 1;

			    getgrouplist("dummy", basegid, groups, &n);

			    return 0;
			}
			EOF
		then
			GRP_T="$grp_t"
			break
		fi
	done
	unset grp_t
fi
: "${GRP_T:="$(getparamtype unistd.h getgrouplist 3 | sed 's/ *\*//')"}"

# shellcheck disable=2034
case $GRP_T in
("short")		MAX_GRP_VAL=SHRT_MAX ;;
("unsigned short")	MAX_GRP_VAL=USHRT_MAX ;;
("int")			MAX_GRP_VAL=INT_MAX ;;
("unsigned int")	MAX_GRP_VAL=UINT_MAX ;;
("long")		MAX_GRP_VAL=LONG_MAX ;;
("unsigned long")	MAX_GRP_VAL=ULONG_MAX ;;
("long long")		MAX_GRP_VAL=LLONG_MAX ;;
("unsigned long long")	MAX_GRP_VAL=ULLONG_MAX ;;
("intmax_t")		MAX_GRP_VAL=INTMAX_MAX ;;
("uintmax_t")		MAX_GRP_VAL=UINTMAX_MAX ;;
("gid_t")		MAX_GRP_VAL=MAX_GID_VAL ;;
(*)			err 'line %s: unrecognised group type' "${LINENO-???}"
esac

if [ "${GRP_T-}" ]
then
	addcppflags -D"GRP_T=$GRP_T" -D"MAX_GRP_VAL=$MAX_GRP_VAL"
	printf '%s ' "-DGRP_T=$GRP_T" "-DMAX_GRP_VAL=$MAX_GRP_VAL"
fi

# setgroups number of groups data type
: "${NGRPS_T=}"
# shellcheck disable=2086
if ! [ "$NGRPS_T" ] && [ "$ccwconv" ]
then
	IFS=:
	for ngrps_t in $sizetypes
	do
		unset IFS
		[ "$ngrps_t" ] || continue

		# shellcheck disable=2086
		if testcc $wconvflags - <<-EOF
			#define _BSD_SOURCE
			#define _DARWIN_C_SOURCE
			#define _DEFAULT_SOURCE
			#define _GNU_SOURCE

			#include <sys/types.h>
			#include <grp.h>
			#include <unistd.h>

			int
			main(void)
			{
			    $ngrps_t ngroups = 0;
			    gid_t gidset[1] = {0};

			    (void) setgroups(ngroups, gidset);

			    return 0;
			}
			EOF
		then
			NGRPS_T="$ngrps_t"
			break
		fi
	done
	unset ngrps_t
fi
: "${NGRPS_T:="$(getparamtype unistd.h setgroups 1 | sed 's/ *\*//')"}"

# shellcheck disable=2034
case $NGRPS_T in
("short")		MAX_NGRPS_VAL=SHRT_MAX ;;
("unsigned short")	MAX_NGRPS_VAL=USHRT_MAX ;;
("int")			MAX_NGRPS_VAL=INT_MAX ;;
("unsigned int")	MAX_NGRPS_VAL=UINT_MAX ;;
("long")		MAX_NGRPS_VAL=LONG_MAX ;;
("unsigned long")	MAX_NGRPS_VAL=ULONG_MAX ;;
("long long")		MAX_NGRPS_VAL=LLONG_MAX ;;
("unsigned long long")	MAX_NGRPS_VAL=ULLONG_MAX ;;
("intmax_t")		MAX_NGRPS_VAL=INTMAX_MAX ;;
("uintmax_t")		MAX_NGRPS_VAL=UINTMAX_MAX ;;
("size_t")		MAX_NGRPS_VAL=SIZE_MAX ;;
(*)			err 'line %s: unrecognised size type' "${LINENO-???}"
esac

if [ "${NGRPS_T-}" ]
then
	addcppflags "-DNGRPS_T=$NGRPS_T" "-DMAX_NGRPS_VAL=$MAX_NGRPS_VAL"
	printf '%s ' "-DNGRPS_T=$NGRPS_T" "-DMAX_NGRPS_VAL=$MAX_NGRPS_VAL"
fi

# Maximum values representable as uid_t and gid_t.
for id_t in uid_t gid_t
do
	case $id_t in
	(uid_t) id_var=MAX_UID_VAL ;;
	(gid_t) id_var=MAX_GID_VAL ;;
	(*)     err  'line %d: %s: Unknown type' ${LINENO-?} "$id_t"
	esac

	if eval "! [ \"\${$id_var-}\" ]"
	then
		id_bin="$TMPDIR/max_${id_t}_val"

		# shellcheck disable=2034
		if "$CC" -o"$id_bin" -xc -D"T=$id_t" - <<-'EOF' >/dev/null 2>&1
		#include <sys/types.h>
		#include <inttypes.h>
		#include <limits.h>
		#include <stdio.h>

		#define START (CHAR_MAX + 1)

		int
		main(void)
		{
		    uintmax_t i;

		    for (i = START; i > 0 && (T) (i - 1) == (i - 1); i *= 2) {
			if ((T) -1 < (T) 0) {
			    printf("%j\n", (intmax_t) i - 1);
			} else {
			    printf("%ju\n", (uintmax_t) i - 1);
			}
		    }

		    return 0;
		}
		EOF
		then
			id_max="$("$id_bin" | tail -n1)" &&
			eval "$id_var=\"\$id_max\""
		fi
		
		unset id_bin
	fi

	# The working directory could be mounted noexec.
	if eval "! [ \"\${$id_var-}\" ]" && [ "$ccwconv" ]
	then
		for id_max in		\
			SHRT_MAX	\
			USHRT_MAX	\
			INT_MAX		\
			UINT_MAX	\
			LONG_MAX	\
			ULONG_MAX	\
			LLONG_MAX	\
			ULLONG_MAX
		do
			# shellcheck disable=2086
			testcc $wconvflags - <<-EOF || break
			#include <sys/types.h>
			#include <inttypes.h>
			#include <limits.h>

			int
			main(void)
			{
			    $id_t var = $id_max;
			    return 0;
			}
			EOF
			eval "$id_var=\"\$id_max\""
		done
	fi
	unset id_bin id_max id_var
done
unset id_t

if [ "${MAX_UID_VAL-}" ]
then
	addcppflags -D"MAX_UID_VAL=$MAX_UID_VAL"
	printf '%s ' "-DMAX_UID_VAL=$MAX_UID_VAL"
fi

if [ "${MAX_GID_VAL-}" ]
then
	addcppflags -D"MAX_GID_VAL=$MAX_GID_VAL"
	printf '%s ' "-DMAX_GID_VAL=$MAX_GID_VAL"
fi

echo


#
# Compiler flags
#

printf 'CFLAGS = '

# Common flags
if [ "${CFLAGS-x}" = x ] && [ "${CFLAGS-}" != x ]
then
	if [ "$ccname" = c99 ]
	then
		CFLAGS=-O1
		printf '%s ' "$CFLAGS"
	else
		eval specflags="\${cflags_$ccname-}"
		CFLAGS=
		# shellcheck disable=2086,2154
		for flag in $cflags $specflags
		do
			# shellcheck disable=2086
			if testflags $CFLAGS $flag
			then
				setadd CFLAGS $flag
				printf -- '%s ' $flag
			fi
		done
		unset flag specflags
	fi
else
	printf '%s ' "$CFLAGS"
fi
unset cflags

: "${CFLAGS:=}"
CFLAGS="${CFLAGS# }"

echo


#
# Linker flags
#

printf 'LDFLAGS = '

if [ "${LDFLAGS-x}" = x ] && [ "${LDFLAGS-}" != x ]
then
	LDFLAGS=
	if [ "$ccname" = c99 ]
	then
		# shellcheck disable=2086
		printf '%s ' $LDFLAGS
	else
		eval specflags="\${ldflags_$ccname-}"
		# shellcheck disable=2086,2154
		for flag in $ldflags $specflags
		do
			# shellcheck disable=2086
			if testflags $LDFLAGS $CFLAGS $flag
			then
				setadd LDFLAGS $flag
				printf -- '%s ' $flag
			fi
		done
		unset flag specflags
	fi
else
	printf '%s ' "$LDFLAGS"
fi
unset ldflags

: "${LDFLAGS:=}"
LDFLAGS="${LDFLAGS# }"

echo


#
# Archiver
#

printf 'AR = '

if [ "${AR-x}" = x ] && ! [ "${AR-}" = x ]
then
	ccvers="$(
		"$CC" -v 2>&1 |
		awk '{
			for (i = 1; i <= NF; i++) {
				if ($i ~ /^v?[0-9]+\.[0-9.]+$/) {
					gsub("[^0-9\.]", "", $i);
					print $i;
					exit;
				}
			}
		}' 2>/dev/null
	)"

	if [ "$ccvers" ]
	then
		ccmajor="${ccvers%%.*}"
	else
		case $ccexec in
		(*[0-9]*) ccmajor="${ccexec#"${ccexec%%[0-9]*}"}" ;;
		(*)       ccmajor= ;;
		esac
	fi
	readonly ccvers ccmajor

	if [ "${ccmajor-}" ]
	then archivers="$ccpre-ar-$ccmajor $ccpre-ar$ccmajor $ccpre-ar"
	else archivers="$ccpre-ar"
	fi

	for archiver in $archivers
	do
		unset IFS
		if command -v "$archiver" >/dev/null 2>&1
		then
			AR="$archiver"
			break
		fi
	done
	unset archiver archivers
fi
: "${AR:="ar"}"

printf '%s\n' "$AR"


#
# Archiver flags
#

printf 'ARFLAGS = '

if [ "${ARFLAGS-x}" = x ] && ! [ "${ARFLAGS-}" = x ]
then
	file="$TMPDIR/file"
	archive="$file.a"
	touch "$file"
	if "$AR" -Ucru "$archive" "$file" >/dev/null 2>&1
	then ARFLAGS=-Ucru
	else ARFLAGS=-cru
	fi
	unset file archive
fi
: "${ARFLAGS:="-cru"}"

printf '%s\n' "$ARFLAGS"


#
# Pre-load variable
#

printf 'preload = '

case $(uname) in
(Darwin) preload=DYLD_INSERT_LIBRARIES ;;
(*)      preload=LD_PRELOAD ;;
esac

printf '%s\n' "$preload"


# #
# # Check which libraries are available
# #
#
# printf 'Libraries:'
#
#
# # shellcheck disable=2086,2154
# set -- $libs
# nlibs=0 njobs=0 jobs=''
# while [ "$njobs" -gt 0 ] || [ $# -gt 0 ]
# do
# 	# Start jobs.
# 	while [ "$njobs" -lt "$maxnjobs" ] && [ $# -gt 0 ]
# 	do
# 		lib="$1"
# 		shift
#
# 		libvar="SUCGI_HAVE_LIB$(
# 			printf '%s\n' "$lib"	|
# 			sed 's/[^a-z]/_/g'	|
# 			tr '[:lower:]' '[:upper:]'
# 		)"
#
# 		case $libvar in (SUCGI_HAVE_LIB_|*[!A-Z0-9_]*)
# 			warn '%s: could not derive macro name.'
# 			continue
# 		esac
#
# 		eval libavail="\${$libvar-}"
#
# 		# shellcheck disable=2154
# 		case $libavail in
# 		(0)
# 			eval "$libvar=0"
# 			;;
# 		(1)
# 			eval "$libvar=1"
# 			nlibs=$((nlibs + 1))
# 			printf ' %s' "$lib"
# 			;;
# 		(*)
# 			src="$TMPDIR/lib.c"
# 			printf 'int main(void) {return 0;}\n' >"$src"
#
# 			"$CC" -o"${src%.c}.out" "$src" -l"$lib" 2>/dev/null &
# 			njobs=$((njobs + 1)) jobs="$jobs$! "
# 			eval "lib_$!"='$lib'
# 			eval "libvar_$!"='$libvar'
# 		esac
#
# 		unset lib libvar libavail
# 	done
#
# 	# Collect the job on top of the pile.
# 	if [ "$njobs" -gt 0 ]
# 	then
# 		job="${jobs%% *}"
# 		eval lib="\"\${lib_$job-}\""
# 		eval libvar="\"\${libvar_$job-}\""
# 		unset "lib_$job" "libvar_$job"
#
# 		if wait "$job"
# 		then
# 			eval "$libvar=1"
# 			LDLIBS="${LDLIBS-} -l$lib" nlibs=$((nlibs + 1))
# 			printf -- ' %s' "$lib"
# 		else
# 			eval "$libvar=0"
# 		fi
#
# 		njobs=$((njobs - 1)) jobs="${jobs#* }"
# 		unset lib libvar job
# 	fi
# done
#
# case $nlibs in
# (0) printf ' none found\n' ;;
# (*) printf '\n' ;;
# esac
#
#
# #
# # How to validate accounts
# #
#
# printf 'Account validation: '
#
# if	[ "${SUCGI_HAVE_SECURITY_PAM_APPL_H-0}" -ne 0 ] &&
# 	[ "${SUCGI_HAVE_LIBPAM-0}" -ne 0 ]
# then
# 	printf 'PAM\n'
# elif	[ "${SUCGI_HAVE_SHADOW-0}" -ne 0 ]
# then
# 	printf 'shadow\n'
# else
# 	if ! [ "${SUCGI_HAVE_EXPIRE-}" ]
# 	then
# 		src="$TMPDIR/pw_expire.c"
# 		cat <<-'EOF' >"$src"
# 			#define _BSD_SOURCE
# 			#define _DARWIN_C_SOURCE
# 			#define _DEFAULT_SOURCE
# 			#define _GNU_SOURCE
#
# 			#include <sys/types.h>
# 			#include <pwd.h>
#
# 			struct passwd pwd = {.pw_expire = 0};
# 		EOF
#
# 		if "$CC" -c -o"$TMPDIR/pw_expire.o" "$src" >/dev/null 2>&1
# 		then SUCGI_HAVE_EXPIRE=1
# 		else SUCGI_HAVE_EXPIRE=0
# 		fi
# 	fi
#
# 	case ${SUCGI_HAVE_EXPIRE-0} in
# 	(0) printf 'not supported\n' ;;
# 	(*) printf 'passwd\n' ;;
# 	esac
# fi



#
# Save configuration
#

if [ -e "$confsave" ]
	then exists=x
	else exists=
fi

if [ "$progname" != "$confsave" ]
then
	if [ "$exists" ] && ! [ "$force" ]
	then
		err '%s exists, use --force to overwrite' "$confsave"
	else
		swap="$TMPDIR/$confsave"

		cat <<-EOF >"$swap"
		#!/bin/sh
		# Configuration detected by $srcdir/configure.

		'$srcdir/configure' --force --quiet \\
		EOF

		for key in $macros
		do
			eval "value=\"\${$key--}\""
			! [ "$value" = - ] &&
			printf "\t'%s=%s' \\\\\n" "$key" "${value# }" >>"$swap"
		done
		unset key value
		printf '\t"$@"\n' >>"$swap"

		chmod +x "$swap"
		swapfiles "$confsave" "$swap"
	fi
fi


#
# Create the Makefile
#

if [ -e Makefile ] && ! [ "$force" ]
then err 'Makefile exists, use --force to overwrite'
fi

swap="$TMPDIR/Makefile"
cat <<EOF >"$swap"
#
# This file was generated by ./configure.
#
# DO NOT EDIT THIS FILE! IT WILL GET OVERWRITTEN!
# Edit Makefile.in instead and run ./config.status to update this file.
# See docs/build.md for details.
#

EOF

# Normalise key=value pairs
# TODO: Doesn't sed understand \t or [:space:]?
sed 's/^\([A-Za-z_][A-Za-z_]*\)[ 	]*=/\1 =/' "$srcdir/Makefile.in" |
while IFS= read -r line
do
	case $line in
	(*=*)	key="${line%% =*}"
		case $key in
		(''|[!A-Za-z_]*|*[!0-9A-Za-z_]*) : ;;
		(*)	# shellcheck disable=2086
			if inlist '=' "$key" $macros
			then
				eval value="\"\${$key-}\""
				if [ "$value" ]
				then
					printf '%s = %s\n' "$key" "$value"
					continue
				fi
			fi
		esac
	esac
	printf '%s\n' "$line"
done >>"$swap"
unset rawkey key value

swapfiles Makefile "$swap"
unset swap
